#include "DirecLightShadowMapData.h"

#include "VirtualCamera.h"

DirecLightShadowMapData::DirecLightShadowMapData(ID3D11Device* device, 
	unsigned depthMapHeight, unsigned depthMapWidth, 
	DirectionalLight* directionalLightSource, 
	ShadowMapBoundingSphere* sceneVolume)
{
	//Init D3D pointers. 
	dsv = 0;
	srv = 0;

	//Store the height and width.
	shadowMapHeight = depthMapHeight;
	shadowMapWidth = depthMapWidth;

	assert(shadowMapHeight == shadowMapWidth);

	//Build matricies if values have been passed. 
	if (directionalLightSource && sceneVolume)
		Update(directionalLightSource, sceneVolume);

	//Create the depth buffer
	CreateShadowMapD3D(device);
}

DirecLightShadowMapData::~DirecLightShadowMapData()
{
	//Release D3D resources
	ReleaseCOM(dsv);
	ReleaseCOM(srv);
}

void DirecLightShadowMapData::Update(DirectionalLight* updatedDirectionalLightSource, 
	ShadowMapBoundingSphere* updatedSceneVolume)
{
	//Create view matrix.
	XMVECTOR lightDir = XMLoadFloat3(&updatedDirectionalLightSource->Direction);
	XMVECTOR lightPos = -2.0f*updatedSceneVolume->Radius*lightDir;
	XMVECTOR targetPos = XMLoadFloat3(&updatedSceneVolume->Center);
	XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);

	XMMATRIX V = XMMatrixLookAtLH(lightPos, targetPos, up);
	XMStoreFloat4x4(&viewMatrix, V);

	XMStoreFloat3(&this->lightPos, lightPos);
	XMStoreFloat3(&lightLook, targetPos);

	//Create projection matrix.
	// Transform bounding sphere to light space.
	XMFLOAT3 sphereCenterLS;
	XMStoreFloat3(&sphereCenterLS, XMVector3TransformCoord(targetPos, V));

	// Ortho frustum in light space encloses scene.
	float l = sphereCenterLS.x - updatedSceneVolume->Radius;
	float b = sphereCenterLS.y - updatedSceneVolume->Radius;
	float n = sphereCenterLS.z - updatedSceneVolume->Radius;
	float r = sphereCenterLS.x + updatedSceneVolume->Radius;
	float t = sphereCenterLS.y + updatedSceneVolume->Radius;
	float f = sphereCenterLS.z + updatedSceneVolume->Radius;

	XMMATRIX P = XMMatrixOrthographicOffCenterLH(l, r, b, t, 0.0f, f);
	XMStoreFloat4x4(&projMatrix, P);

	//To texture space transform.
	XMMATRIX T(
		0.5f, 0.0f, 0.0f, 0.0f,
		0.0f, -0.5f, 0.0f, 0.0f,
		0.0f, 0.0f, 1.0f, 0.0f,
		0.5f, 0.5f, 0.0f, 1.0f);

	XMMATRIX S = V*P*T;
	XMStoreFloat4x4(&toTexSpaceMatrix, S);
}

void DirecLightShadowMapData::ManualUpdate(
	DirectionalLight* updatedDirectionalLightSource, 
	float translation,
	VirtualCamera* camera)
{
	//Create view matrix.
	XMVECTOR lightDir = XMLoadFloat3(&updatedDirectionalLightSource->Direction);
	XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);

	//Target is the player in the X Z plane. 
	XMVECTOR targetPos = XMVectorSet(camera->GetPosition().x, camera->GetPosition().y, camera->GetPosition().z, 1.0f);

	//Get the current look vector of the camera 
	XMFLOAT3 look = camera->GetLook();
	look.y = 0.0f;
	XMVECTOR camLook = XMLoadFloat3(&look);
	camLook = XMVector4Normalize(camLook);

	//Update the target position so it acounts for the direction the player is facing. 
	targetPos += (camLook * (translation/2.0f));
	
	//Light source is sufficinetly far away to cover enough of the scene
	XMVECTOR lightPos = targetPos + (-lightDir * translation);

	//Create view matrix.
	XMMATRIX V = XMMatrixLookAtLH(lightPos, targetPos, up);
	XMStoreFloat4x4(&viewMatrix, V);

	XMStoreFloat3(&this->lightPos, lightPos);
	XMStoreFloat3(&lightLook, targetPos);

	//Create projection matrix.
	// Transform bounding sphere to light space.
	XMFLOAT3 sphereCenterLS;
	XMStoreFloat3(&sphereCenterLS, XMVector3TransformCoord(targetPos, V));

	// Ortho frustum in light space encloses scene.
	float l = sphereCenterLS.x - translation;
	float b = sphereCenterLS.y - translation;
	float n = sphereCenterLS.z - translation;
	float r = sphereCenterLS.x + translation;
	float t = sphereCenterLS.y + translation;
	float f = sphereCenterLS.z + translation;

	XMMATRIX P = XMMatrixOrthographicOffCenterLH(l, r, b, t, 0.0f, f);
	XMStoreFloat4x4(&projMatrix, P);

	//To texture space transform.
	XMMATRIX T(
		0.5f, 0.0f, 0.0f, 0.0f,
		0.0f, -0.5f, 0.0f, 0.0f,
		0.0f, 0.0f, 1.0f, 0.0f,
		0.5f, 0.5f, 0.0f, 1.0f);

	XMMATRIX S = V*P*T;
	XMStoreFloat4x4(&toTexSpaceMatrix, S);
}

XMMATRIX DirecLightShadowMapData::GetLightView()
{
	XMMATRIX ret = XMLoadFloat4x4(&viewMatrix);
	return ret;
}
	
XMMATRIX DirecLightShadowMapData::GetLightProj()
{
	XMMATRIX ret = XMLoadFloat4x4(&projMatrix);
	return ret;
}
	
XMMATRIX DirecLightShadowMapData::GetNDCToTex()
{
	XMMATRIX ret = XMLoadFloat4x4(&toTexSpaceMatrix);
	return ret;
}

void DirecLightShadowMapData::CreateShadowMapD3D(ID3D11Device* device)
{
	//Esnrue device is not null
	assert(device);

	//Release old views
	ReleaseCOM(dsv);
	ReleaseCOM(srv);

	//Init viewport
	viewport.TopLeftX = 0;
	viewport.TopLeftY = 0;
	viewport.Width = (float)shadowMapWidth;
	viewport.Height = (float)shadowMapHeight;
	viewport.MinDepth = 0.0f;
	viewport.MaxDepth = 1.0f;

	//Create Texture2D to represent the depth buffer. We will create an SRV and 
	//DSV to it. 
	ID3D11Texture2D* depthTex = 0;

	//Fillout texture description
	D3D11_TEXTURE2D_DESC texDesc;
	texDesc.Width = shadowMapWidth;
	texDesc.Height = shadowMapHeight;
	texDesc.ArraySize = 1;
	texDesc.MipLevels = 1;
	texDesc.Format = DXGI_FORMAT_R24G8_TYPELESS; //view descs MUST not be typeless
	texDesc.SampleDesc.Count = 1;   //No MSAA
	texDesc.SampleDesc.Quality = 0;
	texDesc.Usage = D3D11_USAGE_DEFAULT;
	texDesc.CPUAccessFlags = 0;
	texDesc.MiscFlags = 0;
	texDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE;

	//Create texture
	HR(device->CreateTexture2D(&texDesc, 0, &depthTex));

	//Create DSV - Before, we need to fillout description as we have
	//to decide on a non typeless format. 
	D3D11_DEPTH_STENCIL_VIEW_DESC dssDesc;
	dssDesc.Flags = 0;
	dssDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;  //Standard depth buffer format - SRV will be R24...
	dssDesc.Texture2D.MipSlice = 0;
	dssDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; //Its a texture2D non MSAA

	//Create view
	HR(device->CreateDepthStencilView(depthTex, &dssDesc, &dsv));

	//Create SRV - As above, fillout a descrption
	D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
	srvDesc.Format = DXGI_FORMAT_R24_UNORM_X8_TYPELESS;
	srvDesc.Texture2D.MipLevels = texDesc.MipLevels;
	srvDesc.Texture2D.MostDetailedMip = 0; //First
	srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;

	//Create view
	HR(device->CreateShaderResourceView(depthTex, &srvDesc, &srv));

	//Release the texture - this doesnt actually destroy the texture, just reduces
	//the reference count to the resource (note that the views increment the 
	//reference count)
	ReleaseCOM(depthTex);
}